2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../jucer_Headers.h"
27 #include "jucer_JucerDocument.h"
28 #include "jucer_ObjectTypes.h"
29 #include "../ui/jucer_TestComponent.h"
31 const char* const defaultClassName
= "NewJucerComponent";
32 const char* const defaultParentClasses
= "public Component";
34 static const int timerInterval
= 150;
36 //==============================================================================
37 JucerDocument::JucerDocument()
38 : FileBasedDocument (".cpp", "*.cpp",
39 "Open a Jucer C++ file...",
40 "Save as a Jucer C++ file..."),
41 className (defaultClassName
),
42 parentClasses (defaultParentClasses
),
51 componentOverlayOpacity (0.33f
)
53 resources
.setDocument (this);
55 startTimer (timerInterval
);
56 commandManager
->commandStatusChanged();
59 JucerDocument::~JucerDocument()
61 commandManager
->commandStatusChanged();
64 //==============================================================================
65 void JucerDocument::changed()
67 FileBasedDocument::changed();
68 commandManager
->commandStatusChanged();
71 void JucerDocument::timerCallback()
73 if ((lastFocusedComp
!= Component::getCurrentlyFocusedComponent()
74 || lastClickCounter
!= Desktop::getMouseButtonClickCounter())
75 && ! Component::isMouseButtonDownAnywhere())
77 lastFocusedComp
= Component::getCurrentlyFocusedComponent();
78 lastClickCounter
= Desktop::getMouseButtonClickCounter();
80 getUndoManager().beginNewTransaction();
84 bool JucerDocument::perform (UndoableAction
* const action
, const String
& actionName
)
86 startTimer (timerInterval
);
87 return undoManager
.perform (action
, actionName
);
90 void JucerDocument::refreshAllPropertyComps()
92 if (getComponentLayout() != 0)
93 getComponentLayout()->getSelectedSet().changed();
95 for (int i
= getNumPaintRoutines(); --i
>= 0;)
97 getPaintRoutine (i
)->getSelectedElements().changed();
98 getPaintRoutine (i
)->getSelectedPoints().changed();
102 //==============================================================================
103 void JucerDocument::setClassName (const String
& newName
)
105 if (newName
!= className
106 && makeValidCppIdentifier (newName
, false, false, true).isNotEmpty())
108 className
= makeValidCppIdentifier (newName
, false, false, true);
113 void JucerDocument::setComponentName (const String
& newName
)
115 if (newName
!= componentName
)
117 componentName
= newName
;
122 const String
JucerDocument::getParentClassString() const
124 return parentClasses
;
127 void JucerDocument::setParentClasses (const String
& classes
)
129 if (classes
!= parentClasses
)
131 StringArray parentClassLines
;
132 parentClassLines
.addTokens (classes
, ",", String::empty
);
133 parentClassLines
.trim();
134 parentClassLines
.removeEmptyStrings();
135 parentClassLines
.removeDuplicates (false);
137 for (int i
= parentClassLines
.size(); --i
>= 0;)
139 String
s (parentClassLines
[i
]);
142 if (s
.startsWith ("public ")
143 || s
.startsWith ("protected ")
144 || s
.startsWith ("private "))
146 type
= s
.upToFirstOccurrenceOf (" ", true, false);
147 s
= s
.fromFirstOccurrenceOf (" ", false, false);
149 if (s
.trim().isEmpty())
150 type
= s
= String::empty
;
153 s
= type
+ makeValidCppIdentifier (s
.trim(), false, false, true);
155 parentClassLines
.set (i
, s
);
158 parentClasses
= parentClassLines
.joinIntoString (", ");
163 const String
JucerDocument::getConstructorParams() const
165 return constructorParams
;
168 void JucerDocument::setConstructorParams (const String
& newParams
)
170 if (constructorParams
!= newParams
)
172 constructorParams
= newParams
;
177 const String
JucerDocument::getVariableInitialisers() const
179 return variableInitialisers
;
182 void JucerDocument::setVariableInitialisers (const String
& newInitlialisers
)
184 if (variableInitialisers
!= newInitlialisers
)
186 variableInitialisers
= newInitlialisers
;
191 void JucerDocument::setFixedSize (const bool isFixed
)
193 if (fixedSize
!= isFixed
)
200 void JucerDocument::setInitialSize (int w
, int h
)
205 if (initialWidth
!= w
|| initialHeight
!= h
)
213 //==============================================================================
214 bool JucerDocument::isSnapActive (const bool disableIfCtrlKeyDown
) const throw()
216 return snapActive
!= (disableIfCtrlKeyDown
&& ModifierKeys::getCurrentModifiers().isCtrlDown());
219 int JucerDocument::snapPosition (int pos
) const throw()
221 if (isSnapActive (true))
223 jassert (snapGridPixels
> 0);
224 pos
= ((pos
+ snapGridPixels
* 1024 + snapGridPixels
/ 2) / snapGridPixels
- 1024) * snapGridPixels
;
230 void JucerDocument::setSnappingGrid (const int numPixels
, const bool active
, const bool shown
)
232 if (numPixels
!= snapGridPixels
233 || active
!= snapActive
234 || shown
!= snapShown
)
236 snapGridPixels
= numPixels
;
244 //==============================================================================
245 void JucerDocument::setComponentOverlayOpacity (const float alpha
)
247 if (alpha
!= componentOverlayOpacity
)
249 componentOverlayOpacity
= alpha
;
254 //==============================================================================
255 const String
JucerDocument::getDocumentTitle()
260 //==============================================================================
261 void JucerDocument::addMethod (const String
& base
, const String
& returnVal
, const String
& method
, const String
& initialContent
,
262 StringArray
& baseClasses
, StringArray
& returnValues
, StringArray
& methods
, StringArray
& initialContents
)
264 baseClasses
.add (base
);
265 returnValues
.add (returnVal
);
266 methods
.add (method
);
267 initialContents
.add (initialContent
);
270 void JucerDocument::getOptionalMethods (StringArray
& baseClasses
,
271 StringArray
& returnValues
,
272 StringArray
& methods
,
273 StringArray
& initialContents
) const
275 addMethod ("Component", "void", "visibilityChanged()", "", baseClasses
, returnValues
, methods
, initialContents
);
276 addMethod ("Component", "void", "moved()", "", baseClasses
, returnValues
, methods
, initialContents
);
277 addMethod ("Component", "void", "parentHierarchyChanged()", "", baseClasses
, returnValues
, methods
, initialContents
);
278 addMethod ("Component", "void", "parentSizeChanged()", "", baseClasses
, returnValues
, methods
, initialContents
);
279 addMethod ("Component", "void", "lookAndFeelChanged()", "", baseClasses
, returnValues
, methods
, initialContents
);
280 addMethod ("Component", "bool", "hitTest (int x, int y)", "return true;", baseClasses
, returnValues
, methods
, initialContents
);
281 addMethod ("Component", "void", "broughtToFront()", "", baseClasses
, returnValues
, methods
, initialContents
);
282 addMethod ("Component", "void", "filesDropped (const StringArray& filenames, int mouseX, int mouseY)", "", baseClasses
, returnValues
, methods
, initialContents
);
283 addMethod ("Component", "void", "handleCommandMessage (int commandId)", "", baseClasses
, returnValues
, methods
, initialContents
);
284 addMethod ("Component", "void", "childrenChanged()", "", baseClasses
, returnValues
, methods
, initialContents
);
285 addMethod ("Component", "void", "enablementChanged()", "", baseClasses
, returnValues
, methods
, initialContents
);
287 addMethod ("Component", "void", "mouseMove (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
288 addMethod ("Component", "void", "mouseEnter (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
289 addMethod ("Component", "void", "mouseExit (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
290 addMethod ("Component", "void", "mouseDown (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
291 addMethod ("Component", "void", "mouseDrag (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
292 addMethod ("Component", "void", "mouseUp (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
293 addMethod ("Component", "void", "mouseDoubleClick (const MouseEvent& e)", "", baseClasses
, returnValues
, methods
, initialContents
);
294 addMethod ("Component", "void", "mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY)", "", baseClasses
, returnValues
, methods
, initialContents
);
296 addMethod ("Component", "bool", "keyPressed (const KeyPress& key)", "return false; // Return true if your handler uses this key event, or false to allow it to be passed-on.", baseClasses
, returnValues
, methods
, initialContents
);
297 addMethod ("Component", "bool", "keyStateChanged (const bool isKeyDown)", "return false; // Return true if your handler uses this key event, or false to allow it to be passed-on.", baseClasses
, returnValues
, methods
, initialContents
);
298 addMethod ("Component", "void", "modifierKeysChanged (const ModifierKeys& modifiers)", "", baseClasses
, returnValues
, methods
, initialContents
);
300 addMethod ("Component", "void", "focusGained (FocusChangeType cause)", "", baseClasses
, returnValues
, methods
, initialContents
);
301 addMethod ("Component", "void", "focusLost (FocusChangeType cause)", "", baseClasses
, returnValues
, methods
, initialContents
);
302 addMethod ("Component", "void", "focusOfChildComponentChanged (FocusChangeType cause)", "", baseClasses
, returnValues
, methods
, initialContents
);
303 addMethod ("Component", "void", "modifierKeysChanged (const ModifierKeys& modifiers)", "", baseClasses
, returnValues
, methods
, initialContents
);
304 addMethod ("Component", "void", "inputAttemptWhenModal()", "", baseClasses
, returnValues
, methods
, initialContents
);
307 void JucerDocument::setOptionalMethodEnabled (const String
& methodSigniture
, const bool enable
)
310 activeExtraMethods
.addIfNotAlreadyThere (methodSigniture
);
312 activeExtraMethods
.removeString (methodSigniture
);
317 bool JucerDocument::isOptionalMethodEnabled (const String
& methodSigniture
) const throw()
319 return activeExtraMethods
.contains (methodSigniture
);
323 void JucerDocument::addExtraClassProperties (PropertyPanel
* panel
)
327 //==============================================================================
328 const char* const JucerDocument::jucerCompXmlTag
= "JUCER_COMPONENT";
330 XmlElement
* JucerDocument::createXml() const
332 XmlElement
* doc
= new XmlElement (jucerCompXmlTag
);
334 doc
->setAttribute ("documentType", getTypeName());
335 doc
->setAttribute ("className", className
);
336 doc
->setAttribute ("componentName", componentName
);
337 doc
->setAttribute ("parentClasses", parentClasses
);
338 doc
->setAttribute ("constructorParams", constructorParams
);
339 doc
->setAttribute ("variableInitialisers", variableInitialisers
);
340 doc
->setAttribute ("snapPixels", snapGridPixels
);
341 doc
->setAttribute ("snapActive", snapActive
);
342 doc
->setAttribute ("snapShown", snapShown
);
343 doc
->setAttribute ("overlayOpacity", (double) componentOverlayOpacity
);
344 doc
->setAttribute ("fixedSize", fixedSize
);
345 doc
->setAttribute ("initialWidth", initialWidth
);
346 doc
->setAttribute ("initialHeight", initialHeight
);
348 if (activeExtraMethods
.size() > 0)
350 XmlElement
* extraMethods
= new XmlElement ("METHODS");
351 doc
->addChildElement (extraMethods
);
353 for (int i
= 0; i
< activeExtraMethods
.size(); ++i
)
355 XmlElement
* e
= new XmlElement ("METHOD");
356 extraMethods
->addChildElement (e
);
357 e
->setAttribute ("name", activeExtraMethods
[i
]);
364 bool JucerDocument::loadFromXml (const XmlElement
& xml
)
366 if (xml
.hasTagName (jucerCompXmlTag
)
367 && getTypeName().equalsIgnoreCase (xml
.getStringAttribute ("documentType", ObjectTypes::documentTypeNames
[0])))
369 className
= xml
.getStringAttribute ("className", defaultClassName
);
370 componentName
= xml
.getStringAttribute ("componentName", String::empty
);
371 parentClasses
= xml
.getStringAttribute ("parentClasses", defaultParentClasses
);
372 constructorParams
= xml
.getStringAttribute ("constructorParams", String::empty
);
373 variableInitialisers
= xml
.getStringAttribute ("variableInitialisers", String::empty
);
375 fixedSize
= xml
.getBoolAttribute ("fixedSize", false);
376 initialWidth
= xml
.getIntAttribute ("initialWidth", 300);
377 initialHeight
= xml
.getIntAttribute ("initialHeight", 200);
379 snapGridPixels
= xml
.getIntAttribute ("snapPixels", snapGridPixels
);
380 snapActive
= xml
.getBoolAttribute ("snapActive", snapActive
);
381 snapShown
= xml
.getBoolAttribute ("snapShown", snapShown
);
383 componentOverlayOpacity
= (float) xml
.getDoubleAttribute ("overlayOpacity", 0.0);
385 activeExtraMethods
.clear();
387 XmlElement
* const methods
= xml
.getChildByName ("METHODS");
391 forEachXmlChildElementWithTagName (*methods
, e
, "METHOD")
393 activeExtraMethods
.addIfNotAlreadyThere (e
->getStringAttribute ("name"));
397 activeExtraMethods
.trim();
398 activeExtraMethods
.removeEmptyStrings();
401 getUndoManager().clearUndoHistory();
408 //==============================================================================
409 const String
JucerDocument::loadDocument (const File
& file
)
411 String
error (TRANS("Not a valid Jucer cpp file"));
413 const File
cppFile (file
.withFileExtension (".cpp"));
415 const String
cppFileString (cppFile
.loadFileAsString());
417 resources
.loadFromCpp (file
, cppFileString
);
419 XmlElement
* const xml
= pullMetaDataFromCppFile (cppFileString
);
423 if (loadFromXml (*xml
))
424 error
= String::empty
;
426 error
= TRANS("Couldn't parse the XML section of this file correctly");
434 const String
JucerDocument::saveDocument (const File
& file
)
436 const File
cppFile (file
.withFileExtension (".cpp"));
437 const File
hFile (file
.withFileExtension (".h"));
439 String templateH
, templateCpp
;
441 if (! findTemplateFiles (templateH
, templateCpp
))
442 return TRANS("Couldn't find the required Jucer template files...\n\nMake sure the template files directory is set up correctly in the preferences box.");
444 const bool ok
= writeCodeFiles (hFile
, cppFile
, templateH
, templateCpp
);
445 TestComponent::reloadAll();
448 return String::empty
;
450 return TRANS("Couldn't write to the file.");
453 //==============================================================================
454 const File
JucerDocument::getLastDocumentOpened()
456 return StoredSettings::getInstance()->recentFiles
.getFile (0);
459 void JucerDocument::setLastDocumentOpened (const File
& file
)
461 StoredSettings::getInstance()->recentFiles
.addFile (file
);
464 //==============================================================================
465 XmlElement
* JucerDocument::pullMetaDataFromCppFile (const String
& cpp
)
468 lines
.addLines (cpp
);
470 int startLine
= indexOfLineStartingWith (lines
, "BEGIN_JUCER_METADATA", 0);
474 int endLine
= indexOfLineStartingWith (lines
, "END_JUCER_METADATA", 0);
476 if (endLine
> startLine
)
480 for (int i
= startLine
+ 1; i
< endLine
; ++i
)
481 xmlText
<< lines
[i
] << "\n";
483 XmlDocument
doc (xmlText
);
484 return doc
.getDocumentElement();
491 //==============================================================================
492 void JucerDocument::fillInGeneratedCode (GeneratedCode
& code
) const
494 code
.className
= className
;
495 code
.componentName
= componentName
;
496 code
.parentClasses
= parentClasses
;
497 code
.constructorParams
= constructorParams
;
498 code
.initialisers
.addLines (variableInitialisers
);
500 if (! componentName
.isEmpty())
501 code
.parentClassInitialiser
= "Component (" + quotedString (code
.componentName
) + ")";
503 // call these now, just to make sure they're the first two methods in the list.
504 code
.getCallbackCode (String::empty
, "void", "paint (Graphics& g)", false)
505 << "//[UserPrePaint] Add your own custom painting code here..\n//[/UserPrePaint]\n\n";
507 code
.getCallbackCode (String::empty
, "void", "resized()", false);
509 if (getComponentLayout() != 0)
510 getComponentLayout()->fillInGeneratedCode (code
);
512 fillInPaintCode (code
);
514 XmlElement
* const e
= createXml();
516 code
.jucerMetadata
= e
->createDocument (String::empty
, false, false);
519 resources
.fillInGeneratedCode (code
);
522 << "\n//[UserPreSize]\n//[/UserPreSize]\n";
524 if (initialWidth
> 0 || initialHeight
> 0)
526 << "\nsetSize (" << initialWidth
<< ", " << initialHeight
<< ");\n";
528 code
.getCallbackCode (String::empty
, "void", "paint (Graphics& g)", false)
529 << "//[UserPaint] Add your own custom painting code here..\n//[/UserPaint]";
531 code
.getCallbackCode (String::empty
, "void", "resized()", false)
532 << "//[UserResized] Add your own custom resize handling here..\n//[/UserResized]";
534 // add optional methods
535 StringArray baseClasses
, returnValues
, methods
, initialContents
;
536 getOptionalMethods (baseClasses
, returnValues
, methods
, initialContents
);
538 for (int i
= 0; i
< methods
.size(); ++i
)
540 if (isOptionalMethodEnabled (methods
[i
]))
542 String
& s
= code
.getCallbackCode (baseClasses
[i
], returnValues
[i
], methods
[i
], false);
544 if (! s
.contains ("//["))
546 String
userCommentTag ("UserCode_");
547 userCommentTag
+= methods
[i
].upToFirstOccurrenceOf ("(", false, false).trim();
551 << "] -- Add your code here...\n"
552 << initialContents
[i
];
554 if (initialContents
[i
].isNotEmpty() && ! initialContents
[i
].endsWithChar ('\n'))
565 void JucerDocument::fillInPaintCode (GeneratedCode
& code
) const
567 for (int i
= 0; i
< getNumPaintRoutines(); ++i
)
570 ->fillInGeneratedCode (code
, code
.getCallbackCode (String::empty
, "void", "paint (Graphics& g)", false));
574 //==============================================================================
575 bool JucerDocument::findTemplateFiles (String
& templateH
, String
& templateCpp
) const
577 const File
templateDir (StoredSettings::getInstance()->getTemplatesDir());
579 const File
hTemplate (templateDir
.getChildFile ("jucer_ComponentTemplate.h"));
580 const File
cppTemplate (templateDir
.getChildFile ("jucer_ComponentTemplate.cpp"));
582 if (! (cppTemplate
.existsAsFile() && hTemplate
.existsAsFile()))
585 templateH
= hTemplate
.loadFileAsString();
586 templateCpp
= cppTemplate
.loadFileAsString();
588 const String
jucerVersionString ("Jucer version: " + String (JUCER_MAJOR_VERSION
)
589 + "." + String (JUCER_MINOR_VERSION
));
591 // This checks the template files to see if they're the ones that shipped with this
592 // version of the jucer. If it fails, you're probably using the wrong ones.
593 // If you're using customised template files, just add the appropriate version line to
594 // their headers to avoid this warning.
595 jassert (templateH
.containsIgnoreCase (jucerVersionString
));
596 jassert (templateCpp
.containsIgnoreCase (jucerVersionString
));
601 void JucerDocument::getPreviewFiles (String
& h
, String
& cpp
)
603 if (! findTemplateFiles (h
, cpp
))
605 h
= cpp
= TRANS("Couldn't find the required Jucer template files...\n\nMake sure the template files directory is set up correctly in the preferences box.");
609 GeneratedCode
generated (this);
610 fillInGeneratedCode (generated
);
611 generated
.includeFilesCPP
.insert (0, getFile().withFileExtension ("h").getFileName());
613 generated
.applyToCode (h
, getClassName(), true);
614 generated
.applyToCode (cpp
, getClassName(), true);
618 static const String
fixNewLines (const String
& s
)
623 for (int i
= 0; i
< lines
.size(); ++i
)
624 lines
.set (i
, lines
[i
].trimEnd());
626 while (lines
.size() > 0 && lines
[lines
.size() - 1].trim().isEmpty())
627 lines
.remove (lines
.size() - 1);
629 return lines
.joinIntoString ("\r\n") + "\r\n";
632 bool JucerDocument::writeCodeFiles (const File
& headerFile
,
637 GeneratedCode
generated (this);
638 fillInGeneratedCode (generated
);
640 generated
.includeFilesCPP
.insert (0, headerFile
.getFileName());
642 String
existingHeader (h
), existingCpp (cpp
);
644 if (headerFile
.existsAsFile())
645 existingHeader
= headerFile
.loadFileAsString();
647 if (cppFile
.existsAsFile())
648 existingCpp
= cppFile
.loadFileAsString();
650 generated
.applyToCode (h
, headerFile
.getFileNameWithoutExtension(), false, existingHeader
);
651 generated
.applyToCode (cpp
, headerFile
.getFileNameWithoutExtension(), false, existingCpp
);
654 cpp
= fixNewLines (cpp
);
656 return headerFile
.replaceWithText (h
, false, false)
657 && cppFile
.replaceWithText (cpp
, false, false);